mod server_builder;
mod deserialize;

use std::collections::{BTreeMap, HashMap, HashSet};
use std::fs::File;
use std::io;
use std::io::{Error, ErrorKind, Read};
use std::path::PathBuf;
use log::info;
use uuid::Uuid;
use serde::Deserialize;
use crate::config::deserialize::{
    EarlyDataUri,from_str_to_address,from_str_to_uuid,default_true,
    from_str_to_security_num,from_str_to_cipher_kind,from_str_to_sni,
    from_str_to_ws_uri,default_random_string,default_v2ray_geosite_path,
    default_v2ray_geoip_path,from_str_to_option_address,default_http2_method,default_backlog,
    from_str_to_http_method,from_str_to_path,from_str_to_grpc_path,default_grpc_path,default_relay_buffer_size
};
use crate::config::server_builder::ConfigServerBuilder;
use crate::proxy::{Address,CipherKind};

#[derive(Deserialize,Clone)]
struct VmessConfig {
    #[serde(deserialize_with = "from_str_to_address")]
    addr: Address,
    #[serde(deserialize_with = "from_str_to_uuid")]
    uuid: Uuid,
    #[serde(
        rename(deserialize = "method"),
        deserialize_with = "from_str_to_security_num"
    )]
    security_num: u8,
    tag: String,
}

#[derive(Deserialize,Clone)]
struct TrojanConfig{
    password: String,
    #[serde(deserialize_with = "from_str_to_address")]
    addr: Address,
    tag: String,
}

#[derive(Deserialize,Clone)]
struct TlsConfig {
    #[serde(deserialize_with = "from_str_to_sni")]
    sni: String,
    cert_file: Option<String>,
    #[serde(default = "default_true")]
    verify_hostname: bool,
    #[serde(default = "default_true")]
    verify_sni: bool,
    tag: String,
}

#[derive(Deserialize,Clone)]
struct WebsocketConfig{
    #[serde(deserialize_with = "from_str_to_ws_uri")]
    uri: EarlyDataUri,
    #[serde(default)]
    early_data_header_name: String,
    #[serde(default)]
    max_early_data: usize,
    #[serde(default)]
    headers: BTreeMap<String,String>,
    tag: String,
}

#[derive(Deserialize,Clone)]
struct ShadowsocksConfig {
    #[serde(deserialize_with = "from_str_to_address")]
    addr: Address,
    password: String,
    #[serde(deserialize_with = "from_str_to_cipher_kind")]
    method: CipherKind,
    tag: String,
}

#[derive(Deserialize,Clone)]
struct BlackHoleConfig{
    tag: String,
}

#[derive(Deserialize)]
struct Outbounds{
    chain: Vec<String>,
    tag: String,
}

#[derive(Deserialize)]
struct Inbounds {
    #[serde(deserialize_with = "from_str_to_address")]
    addr: Address,
    #[serde(default)]
    enable_udp: bool,
    #[serde(default = "default_random_string")]
    tag: String,
}

#[derive(Deserialize,Clone)]
struct DirectConfig{
    tag: String,
}

#[derive(Deserialize)]
struct GeoSiteRules {
    tag: String,
    #[serde(default = "default_v2ray_geosite_path")]
    file_path: PathBuf,
    rules: Vec<String>,
    #[serde(default)]
    use_mph: bool,
}

#[derive(Deserialize)]
struct GeoIpRules{
    tag: String,
    #[serde(default = "default_v2ray_geoip_path")]
    file_path: PathBuf,
    rules: HashSet<String>,
}

#[derive(Deserialize)]
struct IpRoutingRules{
    tag: String,
    cidr_rules: Vec<String>,
}

#[derive(Deserialize)]
struct DomainRoutingRules{
    tag: String,
    #[serde(default)]
    full_rules: Vec<String>,
    #[serde(default)]
    domain_rules: Vec<String>,
    #[serde(default)]
    regex_rules: Vec<String>,
    #[serde(default)]
    substr_rules: Vec<String>,
    #[serde(default)]
    use_mph: bool,
}

#[derive(Deserialize)]
pub(crate) struct DokodemoDoor{
    #[allow(dead_code)]
    #[serde(default)]
    pub tproxy: bool,
    #[serde(deserialize_with = "from_str_to_address")]
    pub addr: Address,
    #[serde(default,deserialize_with = "from_str_to_option_address")]
    pub target_addr: Option<Address>,
}

#[derive(Deserialize,Clone)]
struct Http2Config{
    tag: String,
    hosts: Vec<String>,
    #[serde(default)]
    headers: HashMap<String,String>,
    #[serde(
        default = "default_http2_method",
        deserialize_with = "from_str_to_http_method"
    )]
    method: http::Method,
    #[serde(deserialize_with = "from_str_to_path")]
    path: http::uri::PathAndQuery,
}

#[derive(Deserialize,Clone)]
struct SimpleObfsConfig{
    tag: String,
    #[serde(deserialize_with = "from_str_to_address")]
    host: Address,
}

#[derive(Deserialize,Clone)]
struct GrpcConfig{
    tag: String,
    host: String,
    #[serde(
        rename(deserialize = "service_name"),
        deserialize_with = "from_str_to_grpc_path",
        default = "default_grpc_path"
    )]
    path: http::uri::PathAndQuery,
}


#[derive(Deserialize)]
pub struct Config {
    #[serde(default)]
    enable_api_server: bool,
    #[serde(default = "default_relay_buffer_size")]
    relay_buffer_size: usize,
    #[serde(deserialize_with = "from_str_to_address", default)]
    api_server_addr: Address,

    #[serde(default = "default_backlog")]
    backlog: u32,
    #[serde(default)]
    ss: Vec<ShadowsocksConfig>,
    #[serde(default)]
    tls: Vec<TlsConfig>,
    #[serde(default)]
    vmess: Vec<VmessConfig>,
    #[serde(default)]
    ws: Vec<WebsocketConfig>,
    #[serde(default)]
    trojan: Vec<TrojanConfig>,
    #[serde(default)]
    direct: Vec<DirectConfig>,
    #[serde(default)]
    blackhole: Vec<BlackHoleConfig>,
    #[serde(default)]
    simpleobfs: Vec<SimpleObfsConfig>,
    #[serde(default)]
    dokodemo: Vec<DokodemoDoor>,
    #[serde(default)]
    domain_routing_rules: Vec<DomainRoutingRules>,
    #[serde(default)]
    ip_routing_rules: Vec<IpRoutingRules>,
    #[serde(default)]
    h2: Vec<Http2Config>,
    #[serde(default)]
    grpc: Vec<GrpcConfig>,
    #[serde(default)]
    geosite_rules: Vec<GeoSiteRules>,
    #[serde(default)]
    geoip_rules: Vec<GeoIpRules>,
    #[serde(default)]
    default_outbound: String,

    outbounds: Vec<Outbounds>,
    inbounds: Vec<Inbounds>,
}
impl Config {
    pub fn read_from_file(filename: String) -> io::Result<Config> {
        let mut file = File::open(filename)?;
        let mut config_string = String::new();
        info!("start to read config from file");
        file.read_to_string(&mut config_string)?;
        //println!("{}",&config_string);
        info!("start to reading config from file..");
        let config = toml::from_str::<Config>(&config_string);
        info!("end to read config from file ");
        match config {
            Ok(c) => {
                info!("config read success: {:#?}",&c.api_server_addr);
                Ok(c)},
            Err(e) => Err(Error::new(ErrorKind::Other,e.message())),
        }
    }

    pub fn build_server(mut self) -> io::Result<ConfigServerBuilder> {
        if self.default_outbound.is_empty() {
            if let Some(name) = self.outbounds.first(){
                self.default_outbound = name.tag.clone();
            }
        }
        Ok(ConfigServerBuilder {})
    }
}

#[test]
fn test_parse_config_file(){
    let c = Config::read_from_file("test_config.toml".to_string()).unwrap();
    println!("{:?}",c.api_server_addr);
}